package phaseutil

import (
	"context"

	"github.com/pkg/errors"
	confv1beta1 "gopkg.openfuyao.cn/bkecommon/cluster/api/v1beta1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	clusterv1beta1 "sigs.k8s.io/cluster-api/api/v1beta1"
	bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1"
	controlv1beta1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1"
	"sigs.k8s.io/cluster-api/util"
	"sigs.k8s.io/cluster-api/util/conditions"
	"sigs.k8s.io/controller-runtime/pkg/client"

	bkev1beta1 "gopkg.openfuyao.cn/cluster-api-provider-bke/api/v1beta1"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/annotation"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/clusterutil"
)

type ClusterAPIObjs struct {
	Cluster             *clusterv1beta1.Cluster
	KubeadmControlPlane *controlv1beta1.KubeadmControlPlane
	MachineDeployment   *clusterv1beta1.MachineDeployment
	Machines            []*clusterv1beta1.Machine
}

type MachineAndNode struct {
	Machine *clusterv1beta1.Machine
	Node    confv1beta1.Node
}

// GetClusterAPIClusterByBKECluster get Cluster obj by BKECluster
func GetClusterAPIClusterByBKECluster(ctx context.Context, c client.Client, bkeCluster *bkev1beta1.BKECluster) (*clusterv1beta1.Cluster, error) {
	// get Cluster obj
	cluster, err := util.GetOwnerCluster(ctx, c, bkeCluster.ObjectMeta)
	if err != nil {
		return nil, errors.Wrapf(err, "failed to get owner cluster")
	}
	if cluster == nil {
		return nil, errors.New("Waiting for owner cluster to be initialized")
	}
	return cluster, nil
}

// GetClusterAPIKubeadmControlPlane get KubeadmControlPlane obj
func GetClusterAPIKubeadmControlPlane(ctx context.Context, c client.Client, cluster *clusterv1beta1.Cluster) (*controlv1beta1.KubeadmControlPlane, error) {
	if cluster == nil {
		return nil, errors.New("cluster is nil")
	}

	cp := &controlv1beta1.KubeadmControlPlane{}
	key := client.ObjectKey{
		Namespace: cluster.Spec.ControlPlaneRef.Namespace,
		Name:      cluster.Spec.ControlPlaneRef.Name,
	}
	if err := c.Get(ctx, key, cp); err != nil {
		return nil, err
	}
	return cp, nil
}

// GetClusterAPIMachineDeployment get MachineDeployment obj
func GetClusterAPIMachineDeployment(ctx context.Context, c client.Client, cluster *clusterv1beta1.Cluster) (*clusterv1beta1.MachineDeployment, error) {
	if cluster == nil {
		return nil, errors.New("client is nil")
	}
	md := &clusterv1beta1.MachineDeployment{}
	mdList := &clusterv1beta1.MachineDeploymentList{}
	err := c.List(ctx, mdList, client.InNamespace(cluster.Namespace), client.MatchingLabels{
		clusterv1beta1.ClusterNameLabel: cluster.Name,
	})
	if err != nil {
		return nil, err
	}
	if len(mdList.Items) == 0 {
		return nil, nil
	}
	for _, mdItem := range mdList.Items {
		for _, ref := range mdItem.GetOwnerReferences() {
			if ref.Kind == cluster.Kind && ref.Name == cluster.Name && ref.UID == cluster.UID {
				md = &mdItem
				break
			}
		}
	}

	if md == nil {
		return nil, nil
	}
	return md, nil
}

// GetClusterAPIAssociateObjs get cluster-api associate objs
func GetClusterAPIAssociateObjs(ctx context.Context, c client.Client, cluster *clusterv1beta1.Cluster) (*ClusterAPIObjs, error) {
	if cluster == nil {
		return nil, errors.New("cluster is nil")
	}
	kcp, err := GetClusterAPIKubeadmControlPlane(ctx, c, cluster)
	if err != nil || kcp == nil {
		return nil, errors.Errorf("get kubeadm control plane failed. err: %v", err)
	}

	md, err := GetClusterAPIMachineDeployment(ctx, c, cluster)
	if err != nil || md == nil {
		return nil, errors.Errorf("get machine deployment failed. err: %v", err)
	}

	return &ClusterAPIObjs{
		MachineDeployment:   md,
		KubeadmControlPlane: kcp,
		Cluster:             cluster,
	}, nil
}

// PauseClusterAPIObj add pause Annotations to cluster api obj
func PauseClusterAPIObj(ctx context.Context, c client.Client, obj client.Object, extraAnnotations ...string) error {
	// pause cluster api obj
	annotations := obj.GetAnnotations()
	if annotations == nil {
		annotations = map[string]string{}
	}
	annotations[clusterv1beta1.PausedAnnotation] = ""

	for _, a := range extraAnnotations {
		annotations[a] = ""
	}

	obj.SetAnnotations(annotations)
	if err := c.Update(ctx, obj); err != nil {
		return errors.Errorf("pause cluster api obj failed, obj: %s/%s/%s, err: %v", obj.GetObjectKind().GroupVersionKind().Kind, obj.GetNamespace(), obj.GetName(), err)
	}
	return nil
}

// ResumeClusterAPIObj remove  pause Annotations from cluster api obj
func ResumeClusterAPIObj(ctx context.Context, c client.Client, obj client.Object, extraAnnotations ...string) error {
	// resume cluster api obj
	annotations := obj.GetAnnotations()
	if annotations == nil {
		obj.SetAnnotations(map[string]string{})
	}
	delete(annotations, clusterv1beta1.PausedAnnotation)
	for _, a := range extraAnnotations {
		delete(annotations, a)
	}
	obj.SetAnnotations(annotations)
	if err := c.Update(ctx, obj); err != nil {
		return errors.Wrapf(err, "resume cluster api obj failed, obj: %s/%s/%s, err: %v", obj.GetObjectKind().GroupVersionKind().Kind, obj.GetNamespace(), obj.GetName(), err)
	}
	return nil
}

// GetMachineAssociateBKEMachine get bke machine by machine
func GetMachineAssociateBKEMachine(ctx context.Context, c client.Client, machine *clusterv1beta1.Machine) (*bkev1beta1.BKEMachine, error) {
	bkeMachineRef := machine.Spec.InfrastructureRef

	bkeMachine := &bkev1beta1.BKEMachine{}
	key := client.ObjectKey{
		Namespace: bkeMachineRef.Namespace,
		Name:      bkeMachineRef.Name,
	}
	if err := c.Get(ctx, key, bkeMachine); err != nil {
		return nil, err
	}
	return bkeMachine, nil
}

// NodeToMachine get machine by node
func NodeToMachine(ctx context.Context, c client.Client, bkeCluster *bkev1beta1.BKECluster, node confv1beta1.Node) (*clusterv1beta1.Machine, error) {
	machineLi := &clusterv1beta1.MachineList{}
	providerID := GenerateProviderID(bkeCluster, node)

	filters := GetListFiltersByBKECluster(bkeCluster)

	if err := c.List(ctx, machineLi, filters...); err != nil {
		if apierrors.IsNotFound(err) {
			return nil, err
		}
		return nil, errors.Wrapf(err, "list machine failed, providerID: %s", providerID)
	}
	if len(machineLi.Items) == 0 {
		return nil, errors.Errorf("machine not found, providerID: %s", providerID)
	}
	for _, m := range machineLi.Items {
		machineProviderID := m.Spec.ProviderID
		if clusterutil.IsBocloudCluster(bkeCluster) {
			if v, ok := annotation.HasAnnotation(&m, annotation.BKEMachineProviderIDAnnotationKey); ok && v != "" {
				machineProviderID = &v
			}
		}
		if machineProviderID != nil && *machineProviderID == providerID {
			return &m, nil
		}
	}
	return nil, errors.Errorf("machine not found, providerID: %s", providerID)
}

// MarkMachineForDeletion set delete Annotations for machine
func MarkMachineForDeletion(ctx context.Context, c client.Client, machine *clusterv1beta1.Machine) error {
	as := machine.GetAnnotations()
	if as == nil {
		as = map[string]string{}
	}
	as[clusterv1beta1.DeleteMachineAnnotation] = ""
	machine.SetAnnotations(as)
	return c.Update(ctx, machine)
}

// GetMachineAssociateKubeadmConfig fetches the kubeadm config for given machine
func GetMachineAssociateKubeadmConfig(ctx context.Context, cl client.Client, m *clusterv1beta1.Machine) (*bootstrapv1.KubeadmConfig, error) {
	bootstrapRef := m.Spec.Bootstrap.ConfigRef
	if bootstrapRef == nil {
		return nil, errors.Errorf("failed to retrieve bootstrap config for machine %q: missing configRef", m.Name)
	}
	machineConfig := &bootstrapv1.KubeadmConfig{}
	if err := cl.Get(ctx, client.ObjectKey{Name: bootstrapRef.Name, Namespace: bootstrapRef.Namespace}, machineConfig); err != nil {
		if apierrors.IsNotFound(errors.Cause(err)) {
			return nil, nil
		}
		return nil, errors.Wrapf(err, "failed to retrieve bootstrap config for machine %q", m.Name)
	}
	return machineConfig, nil
}

// ClusterEndDeployed check cluster is end deployed
func ClusterEndDeployed(ctx context.Context, c client.Client, cluster *clusterv1beta1.Cluster, bkeCluster *bkev1beta1.BKECluster) bool {
	if cluster == nil {
		return false
	}
	bkeMachines, err := GetBKEClusterAssociateBKEMachines(ctx, c, bkeCluster)
	if err != nil {
		return false
	}
	expectBootNodeNum := len(bkeCluster.Spec.ClusterConfig.Nodes)
	failedBootNodeNum, successBootNodeNum := CalculateBKEMachineBootNum(bkeMachines)
	// 如果成功的加失败的数量不为spec中的节点数量，说明还有节点没有进行引导
	if successBootNodeNum+failedBootNodeNum != expectBootNodeNum {
		return false
	}
	return true
}

func ClusterAllowTracker(bkeCluster *bkev1beta1.BKECluster, cluster *clusterv1beta1.Cluster) bool {
	// 如果master节点未被初始化，不允许部署addon
	if !conditions.IsTrue(cluster, clusterv1beta1.ControlPlaneInitializedCondition) {
		return false
	}
	// 集群中有一主一从点了就允许部署addon了
	statusWorkerNodes := GetBKENodesFromNodesStatus(bkeCluster.Status.NodesStatus).Worker()
	for _, statusWorkerNode := range statusWorkerNodes {
		if bkeCluster.GetNodeStateFlag(statusWorkerNode.IP, bkev1beta1.NodeBootFlag) {
			return true
		}
	}

	// 集群中有一master/worker 节点了就允许部署addon了
	statusMasterWorkerNodes := GetBKENodesFromNodesStatus(bkeCluster.Status.NodesStatus).MasterWorker()
	for _, statusMasterWorkerNode := range statusMasterWorkerNodes {
		if bkeCluster.GetNodeStateFlag(statusMasterWorkerNode.IP, bkev1beta1.NodeBootFlag) {
			return true
		}
	}

	// 全部节点都被引导才允许
	//expectBootNodeNum := len(bkeCluster.Spec.ClusterConfig.Nodes)
	//failedBootNodeNum, successBootNodeNum := CalculateBKEMachineBootNum(bkeMachines)
	//// 如果成功的加失败的数量不为spec中的节点数量，说明还有节点没有进行引导，不允许部署addon
	//if successBootNodeNum+failedBootNodeNum != expectBootNodeNum {
	//	return false
	//}
	return false
}
